/**
 * @file bsp_i2c_soft.c
 * @brief 软件模拟 I2C 驱动模块（基于 GPIO 开漏输出方式）
 * @details 适用于 MPU6050 等 I2C 从设备的通信，支持起始信号、应答检测、读写字节等操作。
 */

#include "bsp_i2c.h"
#include "bsp_delay.h"

#if defined(MPU6050_Software_I2C)

/**
 * @brief 初始化软件 I2C 所需的 GPIO
 * @note 配置 SDA/SCL 为开漏输出，并发送停止信号以复位总线。
 */
void I2C_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    RCC_IIC_ENABLE;  /* 启用 GPIO 时钟 */

    GPIO_InitStructure.Pin = I2C_SCL_Pin | I2C_SDA_Pin;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;    /* 开漏输出 */
    GPIO_InitStructure.Pull = GPIO_NOPULL;
    GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(I2C_SDA_GPIO_Port, &GPIO_InitStructure);

    /* 发送停止信号，确保总线空闲 */
    I2C_End();
}

/**
 * @brief I2C 时序延时
 * @note 通过调节循环次数控制 I2C 通信速率。
 * @details
 * - 主频 72MHz，Flash 执行，不优化编译时：
 *   - 循环次数 = 10 → 约 205KHz
 *   - 循环次数 = 7 → 约 347KHz
 *   - 循环次数 = 5 → 约 421KHz
 */
static void I2C_Delay(void)
{
    volatile uint8_t i;
    for (i = 0; i < 10; i++);
}

/**
 * @brief 产生 I2C 起始信号
 * @note SDA 从高到低的跳变，且在 SCL 高电平时发生。
 */
void I2C_Start(void)
{
    I2C_Write_SDA(GPIO_PIN_SET);
    I2C_Write_SCL(GPIO_PIN_SET);
    I2C_Delay();
    I2C_Write_SDA(GPIO_PIN_RESET); /* SDA: 1 → 0 */
    I2C_Delay();
    I2C_Write_SCL(GPIO_PIN_RESET);
}

/**
 * @brief 产生 I2C 停止信号
 * @note SDA 从低到高的跳变，且在 SCL 高电平时发生。
 */
void I2C_End(void)
{
    I2C_Write_SDA(GPIO_PIN_RESET);
    I2C_Write_SCL(GPIO_PIN_SET);
    I2C_Delay();
    I2C_Write_SDA(GPIO_PIN_SET); /* SDA: 0 → 1 */
    I2C_Delay();
}

/**
 * @brief 发送应答信号（ACK / NACK）
 * @param ack 0 表示应答 (ACK)，1 表示非应答 (NACK)
 */
void I2C_SendACK(uint8_t ack)
{
    if (ack)
        I2C_Write_SDA(GPIO_PIN_SET);  /* NACK */
    else
        I2C_Write_SDA(GPIO_PIN_RESET); /* ACK */

    I2C_Delay();
    I2C_Write_SCL(GPIO_PIN_SET);      /* 拉高 SCL 发送应答 */
    I2C_Delay();
    I2C_Write_SCL(GPIO_PIN_RESET);
}

/**
 * @brief 读取从机应答信号
 * @retval 0 表示应答 (ACK)，1 表示非应答 (NACK)
 */
uint8_t I2C_ReadACK(void)
{
    uint8_t ret;

    I2C_Write_SDA(GPIO_PIN_SET);  /* 释放 SDA */
    I2C_Delay();
    I2C_Write_SCL(GPIO_PIN_SET);
    I2C_Delay();

    ret = I2C_Read_SDA();         /* 读取应答电平 */

    I2C_Write_SCL(GPIO_PIN_RESET);
    return ret;
}

/**
 * @brief 发送一个字节数据
 * @param data 待发送的数据字节
 * @retval 从机应答状态：0 表示 ACK，1 表示 NACK
 */
uint8_t I2C_SendByte(uint8_t data)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        /* 依次发送从高位到低位 */
        if (data & 0x80)
            I2C_Write_SDA(GPIO_PIN_SET);
        else
            I2C_Write_SDA(GPIO_PIN_RESET);

        I2C_Delay();
        I2C_Write_SCL(GPIO_PIN_SET);
        I2C_Delay();
        I2C_Write_SCL(GPIO_PIN_RESET);

        data <<= 1;
    }

    return I2C_ReadACK(); /* 检测应答 */
}

/**
 * @brief 读取一个字节数据
 * @param ack 是否发送应答信号（0 = ACK，1 = NACK）
 * @retval 接收到的 1 字节数据
 */
uint8_t I2C_ReadByte(uint8_t ack)
{
    uint8_t data = 0;

    I2C_Write_SDA(GPIO_PIN_SET); /* 释放 SDA 以便输入 */

    for (uint8_t i = 0; i < 8; i++)
    {
        data <<= 1;
        I2C_Write_SCL(GPIO_PIN_SET);
        I2C_Delay();

        if (I2C_Read_SDA())
            data++;

        I2C_Write_SCL(GPIO_PIN_RESET);
        I2C_Delay();
    }

    I2C_SendACK(ack);
    return data;
}

#endif /* MPU6050_Software_I2C */
